﻿using System;
using System.Collections.Generic;
using System.Linq;

namespace Gmantis.Controls.DataGrid
{
    public class DataGridSelection
    {
        // Fields
        private List<DataGridCellsRange> _addedRanges;
        private DataGridSelectionMode _mode;
        private int _pendingUpdates;
        private DataGridCellsRangeCollection _ranges;
        private List<DataGridCellsRange> _removedRanges;

        // Events
        public event EventHandler<DataGridSelectionChangedEventArgs> SelectionChanged;

        public event EventHandler<DataGridSelectionChangedEventArgs> SelectionChanging;

        // Methods
        internal DataGridSelection(DataGridModel model)
        {
            Func<int, DataGridRow> indexer = null;
            Func<int> count = null;
            Func<DataGridRow, bool> contains = null;
            Func<IEnumerable<DataGridRow>> enumerable = null;
            Func<int, DataGridColumn> func5 = null;
            Func<int> func6 = null;
            Func<DataGridColumn, bool> func7 = null;
            Func<IEnumerable<DataGridColumn>> func8 = null;
            Func<int, DataGridCell> func9 = null;
            Func<int> func10 = null;
            Func<DataGridCell, bool> func11 = null;
            Func<IEnumerable<DataGridCell>> func12 = null;
            Func<int, DataGridCellsRange> func13 = null;
            Func<int> func14 = null;
            Func<DataGridCellsRange, bool> func15 = null;
            Func<IEnumerable<DataGridCellsRange>> func16 = null;
            this._addedRanges = new List<DataGridCellsRange>();
            this._removedRanges = new List<DataGridCellsRange>();
            this._ranges = new DataGridCellsRangeCollection(model, new List<DataGridCellsRange>(), false);
            this.Model = model;
            if (indexer == null)
            {
                indexer = index => this.Model.Rows[this.GetSelectedRows()[index]];
            }
            if (count == null)
            {
                count = () => this.GetSelectedRows().Count;
            }
            if (contains == null)
            {
                contains = row => this.GetSelectedRows().Contains(row.Index);
            }
            if (enumerable == null)
            {
                enumerable = () => from rowDisplayIndex in this.GetSelectedRows() select this.Model.Rows[rowDisplayIndex];
            }
            this.SelectedRows = new DataGridSelectedItemsCollection<DataGridRow>(indexer, count, contains, enumerable);
            if (func5 == null)
            {
                func5 = index => this.Model.Columns.OrderedColumns[this.GetSelectedColumns()[index]];
            }
            if (func6 == null)
            {
                func6 = () => this.GetSelectedColumns().Count;
            }
            if (func7 == null)
            {
                func7 = column => this.GetSelectedColumns().Contains(column.DisplayIndex);
            }
            if (func8 == null)
            {
                func8 = () => from columnDisplayIndex in this.GetSelectedColumns() select this.Model.Columns.OrderedColumns[columnDisplayIndex];
            }
            this.SelectedColumns = new DataGridSelectedItemsCollection<DataGridColumn>(func5, func6, func7, func8);
            if (func9 == null)
            {
                func9 = index => this.GetSelectedCells().ElementAt<DataGridCell>(index);
            }
            if (func10 == null)
            {
                func10 = () => this.GetSelectedCells().Count<DataGridCell>();
            }
            if (func11 == null)
            {
                func11 = cell => this._ranges.Contains(cell, false);
            }
            if (func12 == null)
            {
                func12 = () => this.GetSelectedCells();
            }
            this.SelectedCells = new DataGridSelectedItemsCollection<DataGridCell>(func9, func10, func11, func12);
            if (func13 == null)
            {
                func13 = index => this._ranges.ElementAt<DataGridCellsRange>(index);
            }
            if (func14 == null)
            {
                func14 = () => this._ranges.Count<DataGridCellsRange>();
            }
            if (func15 == null)
            {
                func15 = range => this._ranges.Contains(range);
            }
            if (func16 == null)
            {
                func16 = () => this._ranges;
            }
            this.SelectedRanges = new DataGridSelectedItemsCollection<DataGridCellsRange>(func13, func14, func15, func16);
        }

        public bool Add(DataGridCell cell)
        {
            if (this.Model.DataGrid != cell.DataGrid)
            {
                throw new ArgumentException("The specified cell must belong to the data grid.");
            }
            return this.AddRange(new DataGridCellsRange(cell), cell);
        }

        public bool Add(DataGridCellsRange range)
        {
            if ((this.Model.DataGrid != range.BottomRightCell.DataGrid) || (this.Model.DataGrid != range.TopLeftCell.DataGrid))
            {
                throw new ArgumentException("The specified range must belong to the data grid.");
            }
            return this.Add(range.TopLeftCell, range.BottomRightCell);
        }

        public bool Add(DataGridCell fromCell, DataGridCell toCell)
        {
            if ((this.Model.DataGrid != fromCell.DataGrid) || (this.Model.DataGrid != toCell.DataGrid))
            {
                throw new ArgumentException("The specified cell must belong to the data grid.");
            }
            DataGridCell topLeftCell = this.Model.GetCell((fromCell.Row.Index <= toCell.Row.Index) ? fromCell.Row : toCell.Row, (fromCell.Column.DisplayIndex <= toCell.Column.DisplayIndex) ? fromCell.Column : toCell.Column);
            DataGridCell cell = this.Model.GetCell((fromCell.Row.Index >= toCell.Row.Index) ? fromCell.Row : toCell.Row, (fromCell.Column.DisplayIndex >= toCell.Column.DisplayIndex) ? fromCell.Column : toCell.Column);
            return this.AddRange(new DataGridCellsRange(topLeftCell, cell), toCell);
        }

        public bool Add(DataGridColumn fromColumn, DataGridColumn toColumn)
        {
            if ((this.Model.DataGrid != fromColumn.DataGrid) || (this.Model.DataGrid != toColumn.DataGrid))
            {
                throw new ArgumentException("The specified column must belong to the data grid.");
            }
            DataGridCell topLeftCell = this.Model.GetCell(this.Model.Rows[0], (fromColumn.DisplayIndex <= toColumn.DisplayIndex) ? fromColumn : toColumn);
            DataGridCell cell = this.Model.GetCell(this.Model.Rows[this.Model.Rows.Count - 1], (fromColumn.DisplayIndex >= toColumn.DisplayIndex) ? fromColumn : toColumn);
            return this.AddRange(new DataGridCellsRange(topLeftCell, cell), this.Model.GetCell(this.Model.Rows[0], toColumn));
        }

        public bool Add(DataGridRow fromRow, DataGridRow toRow)
        {
            if ((this.Model.DataGrid != fromRow.DataGrid) || (this.Model.DataGrid != toRow.DataGrid))
            {
                throw new ArgumentException("The specified row must belong to the data grid.");
            }
            DataGridColumn[] orderedColumns = this.Model.Columns.OrderedColumns;
            if (orderedColumns.Length > 0)
            {
                DataGridCell topLeftCell = this.Model.GetCell((fromRow.Index <= toRow.Index) ? fromRow : toRow, orderedColumns[0]);
                DataGridCell cell = this.Model.GetCell((fromRow.Index >= toRow.Index) ? fromRow : toRow, orderedColumns[orderedColumns.Length - 1]);
                return this.AddRange(new DataGridCellsRange(topLeftCell, cell), this.Model.GetCell(toRow, orderedColumns[0]));
            }
            return false;
        }

        private bool AddRange(DataGridCellsRange range, DataGridCell preferedCell)
        {
            DataGridCellsRange item = this.ConvertToValidRange(range, preferedCell);
            if (item == null)
            {
                return false;
            }
            this.BeginUpdate();
            if (!this.Mode.IsMultiMode())
            {
                this.Clear();
            }
            this._addedRanges.Add(item);
            this.EndUpdate();
            return true;
        }

        private void ApplySelection(DataGridCellsRangeCollection set, List<DataGridCellsRange> addedRanges, List<DataGridCellsRange> removedRanges)
        {
            foreach (DataGridCellsRange range in removedRanges)
            {
                set.Remove(range);
            }
            int index = 0;
            while (index < addedRanges.Count)
            {
                DataGridCellsRange range2 = addedRanges[index];
                DataGridCellsRange range3 = this.ConvertToValidRange(range2, range2.BottomRightCell);
                if (range3 != range2)
                {
                    addedRanges[index] = range3;
                }
                if (!set.Contains(range3, true))
                {
                    set.Add(range3);
                    index++;
                }
                else
                {
                    addedRanges.RemoveAt(index);
                }
            }
        }

        public void BeginUpdate()
        {
            this._pendingUpdates++;
        }

        public void Clear()
        {
            this.Clear(false);
        }

        internal void Clear(bool forceChanges)
        {
            this.BeginUpdate();
            foreach (DataGridCellsRange range in this._ranges)
            {
                if (!this._removedRanges.Contains(range))
                {
                    this._removedRanges.Add(range);
                }
            }
            this.EndUpdate(true, forceChanges);
        }

        private void CoerceSelection(List<DataGridCellsRange> addedRanges, List<DataGridCellsRange> removedRanges)
        {
            foreach (DataGridCellsRange range in addedRanges.ToList<DataGridCellsRange>())
            {
                foreach (DataGridCellsRange range2 in removedRanges.ToList<DataGridCellsRange>())
                {
                    if (range == range2)
                    {
                        addedRanges.Remove(range);
                        removedRanges.Remove(range2);
                    }
                }
            }
        }

        internal bool Contains(DataGridCell cell)
        {
            foreach (DataGridCellsRange range in this._ranges)
            {
                if (range.Contains(cell))
                {
                    return true;
                }
            }
            return false;
        }

        internal bool Contains(DataGridColumn column)
        {
            foreach (DataGridCellsRange range in this._ranges)
            {
                if (range.Contains(column))
                {
                    return true;
                }
            }
            return false;
        }

        internal bool Contains(DataGridRow row)
        {
            foreach (DataGridCellsRange range in this._ranges)
            {
                if (range.Contains(row))
                {
                    return true;
                }
            }
            return false;
        }

        private DataGridCellsRange ConvertToValidRange(DataGridCellsRange range, DataGridCell preferredCell)
        {
            DataGridCellsRange range2 = range;
            switch (this.Mode)
            {
                case DataGridSelectionMode.None:
                    return null;

                case DataGridSelectionMode.SingleCell:
                    if ((range.Columns.Count<DataGridColumn>() <= 1) && (range.Rows.Count<DataGridRow>() <= 1))
                    {
                        return range2;
                    }
                    return new DataGridCellsRange(preferredCell, preferredCell);

                case DataGridSelectionMode.SingleRow:
                    return new DataGridCellsRange(preferredCell.Row);

                case DataGridSelectionMode.SingleColumn:
                    return new DataGridCellsRange(preferredCell.Column);

                case DataGridSelectionMode.SingleRange:
                case DataGridSelectionMode.MultiRange:
                    return range2;

                case DataGridSelectionMode.MultiRow:
                    return new DataGridCellsRange(range.TopLeftCell.Row, range.BottomRightCell.Row);

                case DataGridSelectionMode.MultiColumn:
                    return new DataGridCellsRange(range.TopLeftCell.Column, range.BottomRightCell.Column);
            }
            return range2;
        }

        public void EndUpdate()
        {
            this.EndUpdate(true, false);
        }

        public void EndUpdate(bool commitChanges, bool forceChanges)
        {
            this._pendingUpdates--;
            if (this._pendingUpdates == 0)
            {
                this.CoerceSelection(this._addedRanges, this._removedRanges);
                if ((this._addedRanges.Count > 0) || (this._removedRanges.Count > 0))
                {
                    if (commitChanges)
                    {
                        this.OnSelectionChanging(this._addedRanges, this._removedRanges, forceChanges);
                        if (this.PerformSelection(this._addedRanges, this._removedRanges))
                        {
                            this.OnSelectionChanged(this._addedRanges, this._removedRanges);
                        }
                    }
                    this._addedRanges.Clear();
                    this._removedRanges.Clear();
                }
            }
        }

        private IEnumerable<DataGridCell> GetSelectedCells()
        {
            OrderedSet selectedRows = this.GetSelectedRows();
            OrderedSet selectedColumns = this.GetSelectedColumns();
            for (int i = 0; i < selectedRows.Count; i++)
            {
                int rowIndex = selectedRows[i];
                for (int j = 0; j < selectedColumns.Count; j++)
                {
                    int columnIndex = selectedColumns[j];
                    DataGridCell cell = this.Model.GetCell(rowIndex, columnIndex);
                    if (this._ranges.Contains(cell, false))
                    {
                        yield return cell;
                    }
                }
            }
        }

        private OrderedSet GetSelectedColumns()
        {
            OrderedSet set = new OrderedSet();
            foreach (DataGridCellsRange range in this.SelectedRanges)
            {
                int displayIndex = range.TopLeftCell.Column.DisplayIndex;
                int to = range.BottomRightCell.Column.DisplayIndex;
                if ((displayIndex >= 0) && (to >= 0))
                {
                    set.AddRange(displayIndex, to);
                }
            }
            return set;
        }

        private OrderedSet GetSelectedRows()
        {
            OrderedSet set = new OrderedSet();
            foreach (DataGridCellsRange range in this.SelectedRanges)
            {
                int index = range.TopLeftCell.Row.Index;
                int to = range.BottomRightCell.Row.Index;
                if ((index >= 0) && (to >= 0))
                {
                    set.AddRange(index, to);
                }
            }
            return set;
        }

        private bool IsValid(DataGridCellsRangeCollection collection)
        {
            switch (this.Mode)
            {
                case DataGridSelectionMode.None:
                    return (collection.Count == 0);

                case DataGridSelectionMode.SingleCell:
                case DataGridSelectionMode.SingleRow:
                case DataGridSelectionMode.SingleColumn:
                case DataGridSelectionMode.SingleRange:
                    return (collection.Count <= 1);
            }
            return true;
        }

        private void OnSelectionChanged(List<DataGridCellsRange> addedRanges, List<DataGridCellsRange> removedRanges)
        {
            if (this.SelectionChanged != null)
            {
                this.SelectionChanged(this, new DataGridSelectionChangedEventArgs(this.Model, new List<DataGridCellsRange>(addedRanges), new List<DataGridCellsRange>(removedRanges), true));
            }
        }

        private void OnSelectionChanging(List<DataGridCellsRange> addedRanges, List<DataGridCellsRange> removedRanges, bool forceChanges)
        {
            if (this.SelectionChanging != null)
            {
                this.SelectionChanging(this, new DataGridSelectionChangedEventArgs(this.Model, addedRanges, removedRanges, forceChanges));
            }
        }

        private bool PerformSelection(List<DataGridCellsRange> addedRanges, List<DataGridCellsRange> removedRanges)
        {
            DataGridCellsRangeCollection set = new DataGridCellsRangeCollection(this.Model, this._ranges.ToList<DataGridCellsRange>(), false);
            this.ApplySelection(set, addedRanges, removedRanges);
            if (this.IsValid(set))
            {
                this.ApplySelection(this._ranges, addedRanges, removedRanges);
                return true;
            }
            return false;
        }

        public bool Remove(DataGridCell cell)
        {
            if (this.Model.DataGrid != cell.DataGrid)
            {
                throw new ArgumentException("The specified cell must belong to the data grid.");
            }
            return this.RemoveRange(new DataGridCellsRange(cell), cell);
        }

        public bool Remove(DataGridCell fromCell, DataGridCell toCell)
        {
            if ((this.Model.DataGrid != fromCell.DataGrid) || (this.Model.DataGrid != toCell.DataGrid))
            {
                throw new ArgumentException("The specified cell must belong to the data grid.");
            }
            DataGridCell topLeftCell = this.Model.GetCell((fromCell.Row.Index <= toCell.Row.Index) ? fromCell.Row : toCell.Row, (fromCell.Column.DisplayIndex <= toCell.Column.DisplayIndex) ? fromCell.Column : toCell.Column);
            DataGridCell cell = this.Model.GetCell((fromCell.Row.Index >= toCell.Row.Index) ? fromCell.Row : toCell.Row, (fromCell.Column.DisplayIndex >= toCell.Column.DisplayIndex) ? fromCell.Column : toCell.Column);
            return this.RemoveRange(new DataGridCellsRange(topLeftCell, cell), toCell);
        }

        public bool Remove(DataGridColumn fromColumn, DataGridColumn toColumn)
        {
            if ((this.Model.DataGrid != fromColumn.DataGrid) || (this.Model.DataGrid != toColumn.DataGrid))
            {
                throw new ArgumentException("The specified column must belong to the data grid.");
            }
            DataGridCell topLeftCell = this.Model.GetCell(this.Model.Rows[0], (fromColumn.DisplayIndex <= toColumn.DisplayIndex) ? fromColumn : toColumn);
            DataGridCell cell = this.Model.GetCell(this.Model.Rows[this.Model.Rows.Count - 1], (fromColumn.DisplayIndex >= toColumn.DisplayIndex) ? fromColumn : toColumn);
            return this.RemoveRange(new DataGridCellsRange(topLeftCell, cell), this.Model.GetCell(this.Model.Rows[0], toColumn));
        }

        public bool Remove(DataGridRow fromRow, DataGridRow toRow)
        {
            if ((this.Model.DataGrid != fromRow.DataGrid) || (this.Model.DataGrid != toRow.DataGrid))
            {
                throw new ArgumentException("The specified rows must belong to the data grid.");
            }
            DataGridColumn[] orderedColumns = this.Model.Columns.OrderedColumns;
            if (orderedColumns.Length > 0)
            {
                DataGridCell topLeftCell = this.Model.GetCell((fromRow.Index <= toRow.Index) ? fromRow : toRow, orderedColumns[0]);
                DataGridCell cell = this.Model.GetCell((fromRow.Index >= toRow.Index) ? fromRow : toRow, orderedColumns[orderedColumns.Length - 1]);
                return this.RemoveRange(new DataGridCellsRange(topLeftCell, cell), new DataGridCellsRange(topLeftCell, cell).TopLeftCell);
            }
            return false;
        }

        private bool RemoveRange(DataGridCellsRange range, DataGridCell preferedCell)
        {
            DataGridCellsRange range2 = this.ConvertToValidRange(range, preferedCell);
            if (range2 == null)
            {
                return false;
            }
            this.BeginUpdate();
            foreach (DataGridCellsRange range3 in this._ranges)
            {
                if (range3.Intersects(range2))
                {
                    this._removedRanges.Add(range3);
                    foreach (DataGridCellsRange range4 in range3.Subtract(range2))
                    {
                        this._addedRanges.Add(range4);
                    }
                }
            }
            this.EndUpdate();
            return true;
        }

        public bool SelectAll()
        {
            DataGridRow row = this.Model.Rows.GetVisibleRowsFromTop(true).FirstOrDefault<DataGridRow>(r => r.IsSelectable);
            DataGridColumn column = this.Model.Columns.GetVisibleColumnsFromLeft().FirstOrDefault<DataGridColumn>(c => c.IsSelectable);
            if ((row == null) || (column == null))
            {
                return false;
            }
            DataGridRow row2 = this.Model.Rows.GetVisibleRowsFromBottom(true).FirstOrDefault<DataGridRow>(r => r.IsSelectable);
            DataGridColumn column2 = this.Model.Columns.GetVisibleColumnsFromRight().FirstOrDefault<DataGridColumn>(c => c.IsSelectable);
            this.BeginUpdate();
            this.Clear();
            this.Add(this.Model.GetCell(row, column), this.Model.GetCell(row2, column2));
            this.EndUpdate();
            return true;
        }

        // Properties
        public DataGridSelectionMode Mode
        {
            get
            {
                return this._mode;
            }
            internal set
            {
                this._mode = value;
                this.Clear();
            }
        }

        internal DataGridModel Model { get; private set; }

        public DataGridSelectedItemsCollection<DataGridCell> SelectedCells { get; private set; }

        public DataGridSelectedItemsCollection<DataGridColumn> SelectedColumns { get; private set; }

        public DataGridSelectedItemsCollection<DataGridCellsRange> SelectedRanges { get; private set; }

        public DataGridSelectedItemsCollection<DataGridRow> SelectedRows { get; private set; }
    }
}
